﻿using BEPUphysics.Entities;
using OpenTK;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LDLib
{
    /// <summary>
    /// A base class for all the game objects.
    /// You can either inherit from this class or use it
    /// via composition. Update and render function delegates
    /// are provided for composition purposes.
    /// </summary>
    public class GameObject
    {
        /// <summary>
        /// Name of this game object
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Indicator for whether or not this game object should be rendered.
        /// This is toggled by the bounding sphere - frustum -test.
        /// </summary>
        private bool shouldRender = true;

        /// <summary>
        /// Parent of this game object.
        /// Transformations (rotation, location, scale) are
        /// applied in the local coordate system, which means
        /// that if the game object has a parent, the parent's
        /// transformation is applied first.
        /// </summary>
        public GameObject Parent { get; set; }

        /// <summary>
        /// List of children of this game object.
        /// </summary>
        public List<GameObject> Children { get; private set; }

        /// <summary>
        /// Delegate for pre-update
        /// </summary>
        /// <param name="dt">Time since last call</param>
        public delegate void DelPreUpdate(double dt);

        /// <summary>
        /// Delegate for post-update
        /// </summary>
        /// <param name="dt">Time since last call</param>
        public delegate void DelPostUpdate(double dt);

        /// <summary>
        /// Pre-update delegates
        /// </summary>
        public DelPreUpdate PreUpdate;

        /// <summary>
        /// Post-update delegates
        /// </summary>
        public DelPostUpdate PostUpdate;

        /// <summary>
        /// Mesh used for this game object.
        /// Leave 'null' if you don't need rendering.
        /// </summary>
        public Mesh Mesh { get; set; }

        /// <summary>
        /// Material used to render the mesh.
        /// Leave 'null' if you don't need rendering.
        /// </summary>
        public Material Material { get; set; }

        /// <summary>
        /// Physics body. Contains the BEPUphysics body.
        /// Leave null if you don't need physics for the
        /// object.
        /// </summary>
        public Entity Body { get; set; }

        /// <summary>
        /// Offset between the physics body and the game object
        /// </summary>
        public Vector3 BodyOffset { get; set; }

        /// <summary>
        /// Orientation offset between the physics body and the game object
        /// </summary>
        public Quaternion BodyOrientationOffset { get; set; }

        /// <summary>
        /// Local transform of the game object
        /// </summary>
        public Transform LocalTransform { get; private set; }

        /// <summary>
        /// World transform of the game object
        /// </summary>
        public Transform WorldTransform { get; private set; }

        /// <summary>
        /// Constructs a new game object called 'name'
        /// </summary>
        /// <param name="name">Name of the game object</param>
        public GameObject(string name)
        {
            this.Name = name;
            this.LocalTransform = new Transform();
            this.WorldTransform = new Transform();
            this.Children = new List<GameObject>();
            this.BodyOffset = Vector3.Zero;
        }

        /// <summary>
        /// Update function. Called every update cycle
        /// </summary>
        /// <param name="dt">Time since the last call</param>
        public virtual void Update(double dt)
        {
            // First, invoke the pre-update delegates
            if (PreUpdate != null) PreUpdate.Invoke(dt);

            // Do the actual updating
            if(Body != null)
            {
                Vector3 rotatedOffset = Vector3.Transform(BodyOffset, LocalTransform.Rotation);
                LocalTransform.Location = Body.Position + rotatedOffset;
                LocalTransform.Rotation = Body.Orientation + BodyOrientationOffset;
            }

            // Update the world transform
            if (Parent != null) WorldTransform = Transform.GetWorldTransform(Parent.WorldTransform, LocalTransform);
            else
            {
                WorldTransform.Location = LocalTransform.Location;
                WorldTransform.Rotation = LocalTransform.Rotation;
                WorldTransform.Scale = LocalTransform.Scale;
            }

            // Check the bounding sphere against the camera frustum
            if(Mesh != null)
            {
                if(!Camera.Main.SphereInFrustum(Mesh.Center, Mesh.Radius)) shouldRender = false;
                else shouldRender = true;
            }

            foreach (GameObject child in Children) child.Update(dt);

            // Finally, invoke the post-update delegates
            if (PostUpdate != null) PostUpdate.Invoke(dt);
        }

        public virtual void Render(double dt)
        {
            foreach (GameObject child in Children) child.Render(dt);
            if (shouldRender && Mesh != null) RenderBatch.Add(Material, this);
        }
    }
}
